#include <ctype.h>
#include <string.h>
#include <stdlib/malloc.h>
#include <stdarg.h>

#define	FL_LJUST	0x0001		/* left-justify field */
#define	FL_SIGN		0x0002		/* sign in signed conversions */
#define	FL_SPACE	0x0004		/* space in signed conversions */
#define	FL_ALT		0x0008		/* alternate form */
#define	FL_ZEROFILL	0x0010		/* fill with zero's */
#define	FL_SHORT	0x0020		/* optional h */
#define	FL_LONG		0x0040		/* optional l */
#define	FL_LONGDOUBLE	0x0080		/* optional L */
#define	FL_WIDTHSPEC	0x0100		/* field width is specified */
#define	FL_PRECSPEC	0x0200		/* precision is specified */
#define FL_SIGNEDCONV	0x0400		/* may contain a sign */
#define	FL_NOASSIGN	0x0800		/* do not assign (in scanf) */
#define	FL_NOMORE	0x1000		/* all flags collected */

#define	UQUAD_MAX ((unsigned int)0-1)	  /* max value for a uquad_t */
#define	QUAD_MAX  ((int)(UQUAD_MAX >> 1))
#define	QUAD_MIN  (-QUAD_MAX-1)         /* min value for a quad_t */

#define	NUMLEN	128
#define	NR_CHARS	32

static char	Xtable[NR_CHARS];
static char	inp_buf[NUMLEN];

typedef struct {
  char *str;
  int pos;
} SCANF_T;

unsigned int sc_getc(SCANF_T *st) {
  char result;
  if(st != NULL) {
    result = st->str[st->pos];
    st->pos++;
  } else {
    result = getchar();
  }
  return result;
}

unsigned int sc_ungetc(char c, SCANF_T *st) {
  if(st != NULL)
    st->pos--;
  return 0;
}

int strtoq(const char *nptr, char **endptr, register int base)
{
	register const char *s;
	register unsigned int acc;
	register unsigned char c;
	register unsigned int qbase, cutoff;
	register int neg, any, cutlim;

	s = nptr;
	do {
		c = *s++;
	} while (isspace(c));
	if (c == '-') {
		neg = 1;
		c = *s++;
	} else {
		neg = 0;
		if (c == '+')
			c = *s++;
	}
	if ((base == 0 || base == 16) &&
	    c == '0' && (*s == 'x' || *s == 'X')) {
		c = s[1];
		s += 2;
		base = 16;
	}
	if (base == 0)
		base = c == '0' ? 8 : 10;

	qbase = (unsigned)base;
	cutoff = neg ? -(unsigned int)QUAD_MIN : QUAD_MAX;
	cutlim = cutoff % qbase;
	cutoff /= qbase;
	for (acc = 0, any = 0;; c = *s++) {
		if (!isascii(c))
			break;
		if (isdigit(c))
			c -= '0';
		else if (isalpha(c))
			c -= isupper(c) ? 'A' - 10 : 'a' - 10;
		else
			break;
		if (c >= base)
			break;
		if (any < 0 || acc > cutoff || (acc == cutoff && c > cutlim))
			any = -1;
		else {
			any = 1;
			acc *= qbase;
			acc += c;
		}
	}
	if (any < 0) {
		acc = neg ? QUAD_MIN : QUAD_MAX;
	} else if (neg)
		acc = -acc;
	if (endptr != 0)
		*endptr = (char *)(any ? s - 1 : nptr);
	return (acc);
}

/*
 *  strtouq()
 */
unsigned int strtouq(const char *nptr, char **endptr, register int base) {
	register const char *s = nptr;
	register unsigned int acc;
	register unsigned char c;
	register unsigned int qbase, cutoff;
	register int neg, any, cutlim;

	/*
	 * See strtoq for comments as to the logic used.
	 */
	s = nptr;
	do {
		c = *s++;
	} while (isspace(c));
	if (c == '-') {
		neg = 1;
		c = *s++;
	} else {
		neg = 0;
		if (c == '+')
			c = *s++;
	}
	if ((base == 0 || base == 16) &&
	    c == '0' && (*s == 'x' || *s == 'X')) {
		c = s[1];
		s += 2;
		base = 16;
	}
	if (base == 0)
		base = c == '0' ? 8 : 10;
	qbase = (unsigned)base;
	cutoff = (unsigned int)UQUAD_MAX / qbase;
	cutlim = (unsigned int)UQUAD_MAX % qbase;
	for (acc = 0, any = 0;; c = *s++) {
		if (!isascii(c))
			break;
		if (isdigit(c))
			c -= '0';
		else if (isalpha(c))
			c -= isupper(c) ? 'A' - 10 : 'a' - 10;
		else
			break;
		if (c >= base)
			break;
		if (any < 0 || acc > cutoff || (acc == cutoff && c > cutlim))
			any = -1;
		else {
			any = 1;
			acc *= qbase;
			acc += c;
		}
	}
	if (any < 0) {
		acc = UQUAD_MAX;
	} else if (neg)
		acc = -acc;
	if (endptr != 0)
		*endptr = (char *)(any ? s - 1 : nptr);
	return (acc);
}

/*
 *  o_collect()
 *  Collect a number of characters which constitite an ordinal number.
 *  When the type is 'i', the base can be 8, 10, or 16, depending on the
 *  first 1 or 2 characters. This means that the base must be adjusted
 *  according to the format of the number. At the end of the function, base
 *  is then set to 0, so strtol() will get the right argument.
 */
static char *o_collect(register int c, register SCANF_T *stream, char type,
			int width, int *basep) {
	register char *bufp = inp_buf;
	register int base;

	switch (type) {
	case 'i':	/* i means octal, decimal or hexadecimal */
	case 'x':
	case 'X':	base = 16;	break;
	case 'd':
	case 'u':	base = 10;	break;
	case 'o':	base = 8;	break;
	case 'b':	base = 2;	break;
	}

	if (c == '-' || c == '+') {
		*bufp++ = c;
		if (--width)
		    c = sc_getc(stream);
	}

	if (width && c == '0' && base == 16) {
		*bufp++ = c;
		if (--width)
			c = sc_getc(stream);
		if (c != 'x' && c != 'X') {
			if (type == 'i') base = 8;
		}
		else if (width) {
			*bufp++ = c;
			if (--width)
				c = sc_getc(stream);
		}
	}
	else if (type == 'i') base = 10;

	while (width) {
		if (((base == 10) && isdigit(c))
		    || ((base == 16) && isxdigit(c))
		    || ((base == 8) && isdigit(c) && (c < '8'))
		    || ((base == 2) && isdigit(c) && (c < '2'))) {
			*bufp++ = c;
			if (--width)
				c = sc_getc(stream);
		}
		else break;
	}

	if (width && c != 0) sc_ungetc(c, stream);
	if (type == 'i') base = 0;
	*basep = base;
	*bufp = '\0';
	return bufp - 1;
}

/*
 * the routine that does the scanning
 */

int _doscan(register SCANF_T *stream, const char *format, va_list ap) {
	int		done = 0;	/* number of items done */
	int		nrchars = 0;	/* number of characters read */
	int		conv = 0;	/* # of conversions */
	int		base;		/* conversion base */
	unsigned int	val;		/* an integer value */
	register char	*str;		/* temporary pointer */
	char		*tmp_string;	/* ditto */
	unsigned	width;		/* width of field */
	int		flags;		/* some flags */
	int		reverse;	/* reverse the checking in [...] */
	int		kind;
	register int	ic;		/* the input character */

	if (!*format) return 0;

	while (1) {
		if (isspace(*format)) {
			while (isspace(*format))
				format++;	/* skip whitespace */
			ic = sc_getc(stream);
			nrchars++;
			while (isspace (ic)) {
				ic = sc_getc(stream);
				nrchars++;
			}
			if (ic != 0) sc_ungetc(ic,stream);
			nrchars--;
		}
		if (!*format) break;	/* end of format */

		if (*format != '%') {
			ic = sc_getc(stream);
			nrchars++;
			if (ic != *format++) {
				if (ic != 0) sc_ungetc(ic,stream);
				nrchars--;
				break;	/* error */
			}
			continue;
		}
		format++;
		if (*format == '%') {
			ic = sc_getc(stream);
			nrchars++;
			if (ic == '%') {
				format++;
				continue;
			}
			else break;
		}
		flags = 0;
		if (*format == '*') {
			format++;
			flags |= FL_NOASSIGN;
		}
		if (isdigit (*format)) {
			flags |= FL_WIDTHSPEC;
			for (width = 0; isdigit (*format);)
				width = width * 10 + *format++ - '0';
		}

		switch (*format) {
		case 'h': flags |= FL_SHORT; format++; break;
		case 'l': flags |= FL_LONG; format++; break;
		case 'L': flags |= FL_LONGDOUBLE; format++; break;
		}
		kind = *format;
		if ((kind != 'c') && (kind != '[') && (kind != 'n')) {
			do {
				ic = sc_getc(stream);
				nrchars++;
			} while (isspace(ic));
			if (ic == 0) break;		/* outer while */
		} else if (kind != 'n') {		/* %c or %[ */
			ic = sc_getc(stream);
			if (ic == 0) break;		/* outer while */
			nrchars++;
		}
		switch (kind) {
		default:
			/* not recognized, like %q */
			return conv || (ic != 0) ? done : 0;
		case 'n':
			if (!(flags & FL_NOASSIGN)) {	/* silly, though */
				if (flags & FL_SHORT)
					*va_arg(ap, short *) = (short) nrchars;
				else if (flags & FL_LONG)
					*va_arg(ap, long *) = (long) nrchars;
				else
					*va_arg(ap, int *) = (int) nrchars;
			}
			break;
		case 'b':		/* binary */
		case 'd':		/* decimal */
		case 'i':		/* general integer */
		case 'o':		/* octal */
		case 'u':		/* unsigned */
		case 'x':		/* hexadecimal */
		case 'X':		/* ditto */
			if (!(flags & FL_WIDTHSPEC) || width > NUMLEN)
				width = NUMLEN;
			if (!width) return done;

			str = o_collect(ic, stream, kind, width, &base);
			if (str < inp_buf
			    || (str == inp_buf
				    && (*str == '-'
					|| *str == '+'))) return done;

			/*
			 * Although the length of the number is str-inp_buf+1
			 * we don't add the 1 since we counted it already
			 */
			nrchars += str - inp_buf;

			if (!(flags & FL_NOASSIGN)) {
				if (kind == 'd' || kind == 'i')
				    val = strtoq(inp_buf, &tmp_string, base);
				else
				    val = strtouq(inp_buf, &tmp_string, base);
				if (flags & FL_LONG)
					*va_arg(ap, unsigned long *) = (unsigned long) val;
				else if (flags & FL_SHORT)
					*va_arg(ap, unsigned short *) = (unsigned short) val;
				else
					*va_arg(ap, unsigned *) = (unsigned) val;
			}
			break;
		case 'c':
			if (!(flags & FL_WIDTHSPEC))
				width = 1;
			if (!(flags & FL_NOASSIGN))
				str = va_arg(ap, char *);
			if (!width) return done;

			while (width && ic != 0) {
				if (!(flags & FL_NOASSIGN))
					*str++ = (char) ic;
				if (--width) {
					ic = sc_getc(stream);
					nrchars++;
				}
			}

			if (width) {
				if (ic != 0) sc_ungetc(ic,stream);
				nrchars--;
			}
			break;
		case 's':
			if (!(flags & FL_WIDTHSPEC))
				width = 0xffff;
			if (!(flags & FL_NOASSIGN))
				str = va_arg(ap, char *);
			if (!width) return done;

			while (width && ic != 0 && !isspace(ic)) {
				if (!(flags & FL_NOASSIGN))
					*str++ = (char) ic;
				if (--width) {
					ic = sc_getc(stream);
					nrchars++;
				}
			}
			/* terminate the string */
			if (!(flags & FL_NOASSIGN))
				*str = '\0';
			if (width) {
				if (ic != 0) sc_ungetc(ic,stream);
				nrchars--;
			}
			break;
		case '[':
			if (!(flags & FL_WIDTHSPEC))
				width = 0xffff;
			if (!width) return done;

			if ( *++format == '^' ) {
				reverse = 1;
				format++;
			} else
				reverse = 0;

			for (str = Xtable; str < &Xtable[NR_CHARS]
							; str++)
				*str = 0;

			if (*format == ']') Xtable[*format++] = 1;

			while (*format && *format != ']') {
				Xtable[*format++] = 1;
				if (*format == '-') {
					format++;
					if (*format
					    && *format != ']'
					    && *(format) >= *(format -2)) {
						int c;

						for( c = *(format -2) + 1
						    ; c <= *format ; c++)
							Xtable[c] = 1;
						format++;
					}
					else Xtable['-'] = 1;
				}
			}
			if (!*format || !(Xtable[ic] ^ reverse)) {
				if (ic != 0) sc_ungetc(ic, stream);
				return done;
			}

			if (!(flags & FL_NOASSIGN))
				str = va_arg(ap, char *);

			do {
				if (!(flags & FL_NOASSIGN))
					*str++ = (char) ic;
				if (--width) {
					ic = sc_getc(stream);
					nrchars++;
				}
			} while (width && ic != 0 && (Xtable[ic] ^ reverse));

			if (width) {
				if (ic != 0) sc_ungetc(ic, stream);
				nrchars--;
			}
			if (!(flags & FL_NOASSIGN)) {	/* terminate string */
				*str = '\0';
			}
			break;
		}		/* end switch */
		conv++;
		if (!(flags & FL_NOASSIGN) && kind != 'n') done++;
		format++;
	}
	return conv || (ic != 0) ? done : 0;
}

/*
 *  sscanf()
 */
int sscanf(char *stream, const char *format, ...) {
	va_list ap;
	int retval;
  static SCANF_T _t_scanf;

  _t_scanf.str = stream;
  _t_scanf.pos = 0;

	va_start(ap, format);

	retval = _doscan(&_t_scanf, format, ap);

	va_end(ap);

	return retval;
}

/*
 *  scanf()
 */
int scanf(const char *format, ...) {
	va_list ap;
	int retval;
  static SCANF_T _t_scanf;
  char *stream = malloc(256);

  gets(stream, 255);

  _t_scanf.str = stream;
  _t_scanf.pos = 0;

	va_start(ap, format);

	retval = _doscan(&_t_scanf, format, ap);

	va_end(ap);
	free(stream);

	return retval;
}

